<?php

namespace App\Models\Payment\Platform;

use App\Jobs\PaymentOrderPaid;
use App\Models\Common\Constant;
use App\Models\Payment\PaymentChannel;
use App\Models\Payment\PaymentOrder;
use App\Models\Payment\RefundOrder;
use App\Models\Payment\TradeType;
use Godruoyi\Snowflake\Snowflake;
use GuzzleHttp\Client;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

/**
 * 银联前置
 */
class UnionPay extends BasePlatform implements BasePlatformInterface
{

    protected $appID;
    protected $subAppID;
    protected $key;
    protected $mchID;
    protected $xmlBody;
    protected $snowflake;
    protected $uri = '';

    const NOTIFY_CODE_SUCCESS = 'success';
    const NOTIFY_CODE_FAIL = 'fail';

    const VERSION = '2.0';
    const CHARSET = 'UTF-8';
    const SIGN_TYPE = 'MD5';

    const RULE = [
        'app_id' => 'required|string|min:12',
        'key' => 'required|string|min:12',
        'mch_id' => 'required|string|min:8'
    ];

    const RULE_V1 = [
        'key' => 'required|string|min:12',
        'mch_id' => 'required|string|min:8'
    ];

    const RULE_V2 = [
        'key' => 'required|string|min:12',
        'mch_id' => 'required|string|min:8',
        'app_id' => 'required|string|min:12',
        'sub_app_id' => 'required|string|min:12',
    ];

    const GATEWAY = 'https://qra.95516.com/pay/gateway';

    public function __construct(PaymentChannel $paymentChannel)
    {
        $this->gateway = self::GATEWAY;

        $parameters = $paymentChannel->parameters ?: [];
        $this->appID = Arr::get($parameters, 'app_id');
        $this->key = Arr::get($parameters, 'key');
        $this->mchID = Arr::get($parameters, 'mch_id');
        $this->subAppID = Arr::get($parameters, 'sub_app_id');

        $this->logger = Log::channel('payment-unionpay');

        $this->snowflake = new Snowflake();
    }

    protected static function nonceStr(): string
    {
        return Str::random();
    }

    protected static function timestamp(): string
    {
        return '';
    }

    protected function signature(): string
    {
        $queryBody = $this->body;
        $signature = $this->mkSignature($queryBody);
        $this->body['sign'] = $signature;
        return $signature;
    }

    private function mkSignature(array $queryBody): string
    {
        ksort($queryBody);
        $appendStr = joinStr($queryBody);
        $appendStr .= "&key=" . $this->key;
        return strtoupper(md5($appendStr));
    }

    public function fire()
    {
        $httpClient = new Client(['base_uri' => $this->gateway, 'timeout' => self::TIMEOUT, 'verify' => false, 'headers' => ['Content-Type' => 'text/xml; charset=UTF8']]);
        $httpMethod = strtoupper($this->method);
        $respBody = [];

        $logPacket = ['gateway' => $this->gateway, 'queryBody' => $this->body];

        try {
            $responseObj = $httpClient->request($httpMethod, $this->uri, ['body' => $this->xmlBody]);
            $respBodyContents = $responseObj->getBody()->getContents();
            $respBody = xml2Arr($respBodyContents);
            $logPacket['response'] = $respBody ?: $respBodyContents;
        } catch (\Throwable $exception) {
            $logPacket['err'] = $exception->getMessage();
        } finally {

            $this->logger->info($this->name, $logPacket);

            if (isset($exception)) {
                throw $exception;
            }
        }

        $this->responseAdapter($respBody);

        return $respBody;
    }

    static public function optionalDatValidator(string $tradeType, array $optionalDat): array
    {
        return [];
    }

    public function toPay(string $tradeType, PaymentOrder $paymentOrder)
    {
        if ($paymentOrder->paid_at || $paymentOrder->order_status === Constant::ORDER_STATUS_PAID) {
            throw new \Exception($paymentOrder->paid_code . '已完成支付的订单不可重复发起支付');
        }

        $this->name = '申请支付参数:' . $tradeType;
        $this->nonceStr = self::nonceStr();
        $timeNow = now();

        $toPayBody = [
            'body' => $paymentOrder->order_title,
            'out_trade_no' => $paymentOrder->paid_code,
            'total_fee' => $paymentOrder->amount,
            'mch_create_ip' => request()->getClientIp(),
            'notify_url' => $this->notifyUrl($paymentOrder->pay_type, $paymentOrder->payment_channel_id),
            'nonce_str' => $this->nonceStr,
            'mch_id' => $this->mchID,
            'version' => self::VERSION,
            'sign_type' => self::SIGN_TYPE,
            'charset' => self::CHARSET,
            'time_start' => (clone $timeNow)->format('YmdHis'),
            'time_expire' => (clone $timeNow)->addMinutes(10)->format('YmdHis')
        ];

        switch ($tradeType) {
            case TradeType::WECHAT_NATIVE:
            case TradeType::ALI_NATIVE:
                $toPayBody['service'] = 'unified.trade.native';
                $toPayBody['sub_appid'] = $this->appID;
                break;
            case TradeType::WECHAT_JSAPI:
            case TradeType::WECHAT_MINI:
                $toPayBody['service'] = 'pay.weixin.jspay';
                $toPayBody['sub_appid'] = $this->appID;
                $toPayBody['is_raw'] = 1;
                $toPayBody['sub_openid'] = $paymentOrder->payer_id;
                if ($paymentOrder->pay_type === PaymentChannel::CATEGORY_UNIONPAY_WECHATPAY_MINI) {
                    $toPayBody['is_minipg'] = 1;
                }
                if (!$paymentOrder->payer_id) {
                    throw new \Exception('openid为空');
                }
                break;
            case TradeType::ALI_JSAPI:
                $toPayBody['service'] = 'pay.alipay.jspay';
                $toPayBody['buyer_id'] = $paymentOrder->payer_id;
                break;
            case TradeType::WECHAT_APP:
                $toPayBody['service'] = 'pay.weixin.raw.app';
                $toPayBody['sub_appid'] = $this->subAppID;
                $toPayBody['appid'] = $this->appID;
                break;
            default:
                throw new \Exception('不支持的支付方式');
        }

        $this->body = $toPayBody;
        $this->signature();

        $this->xmlBody = arr2xml($this->body);
        $payResponse = $this->fire();

        $error = '';
        if (Arr::get($payResponse, 'return_code') === 'SUCCESS' && Arr::get($payResponse, 'result_code') === 'SUCCESS') {
        } else {
            $error = Arr::get($payResponse, 'return_msg') ?: '未知原因';
        }
        $paymentOrder->error = $error;
        $paymentOrder->save();

        return $payResponse;
    }

    public function toQuery(PaymentOrder $paymentOrder)
    {
        $this->name = '订单查询';
        $this->nonceStr = self::nonceStr();

        $this->body = [
            'out_trade_no' => $paymentOrder->paid_code,
            'mch_id' => $this->mchID,
            'version' => self::VERSION,
            'sign_type' => self::SIGN_TYPE,
            'charset' => self::CHARSET,
            'service' => 'unified.trade.query',
            'nonce_str' => $this->nonceStr
        ];

        $this->signature();

        $this->xmlBody = arr2xml($this->body);
        return $this->fire();
    }

    public function toRefund(RefundOrder $refundOrder)
    {
        $this->name = '订单退款';
        $this->nonceStr = self::nonceStr();

        $this->body = [
            'out_trade_no' => $refundOrder->paid_code,
            'out_refund_no' => $refundOrder->refund_code,
            'mch_id' => $this->mchID,
            'version' => self::VERSION,
            'sign_type' => self::SIGN_TYPE,
            'charset' => self::CHARSET,
            'service' => 'unified.trade.refund',
            'total_fee' => $refundOrder->refund_amount,
            'refund_fee' => $refundOrder->refund_amount,
            'refund_channel' => 'ORIGINAL',
            'nonce_str' => $this->nonceStr,
            'op_user_id' => $this->mchID
        ];
        $this->signature();

        $this->xmlBody = arr2xml($this->body);
        return $this->fire();
    }

    public function toClose(PaymentOrder $paymentOrder)
    {
        $this->name = '订单关闭';
        $this->nonceStr = self::nonceStr();

        $this->body = [
            'out_trade_no' => $paymentOrder->paid_code,
            'mch_id' => $this->mchID,
            'version' => self::VERSION,
            'sign_type' => self::SIGN_TYPE,
            'charset' => self::CHARSET,
            'service' => 'unified.trade.close',
            'nonce_str' => $this->nonceStr
        ];

        $this->signature();

        $this->xmlBody = arr2xml($this->body);
        return $this->fire();
    }

    public function handleNotify(PaymentOrder $paymentOrder, array $notifyDat)
    {
        $paymentSuccessful = false;
        $logName = $paymentOrder->pay_type . ':' . $paymentOrder->payment_channel_id;

        if (!$this->notifySignCheck($notifyDat)) {
            Log::channel('payment-notify-error')->info($logName, ['err' => '支付回调验签失败', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        if (!$outTransactionID = Arr::get($notifyDat, 'out_transaction_id')) {
            Log::channel('payment-notify-error')->info($logName, ['err' => '支付回调缺失重要参数流水单号', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        if ($paymentOrder->order_status === Constant::ORDER_STATUS_PAID) {
            if ($paymentOrder->out_trade_no && ($paymentOrder->out_trade_no !== $outTransactionID)) {
                Log::channel('payment-notify-error')->info($logName, ['err' => '订单重复支付(out_transaction_id)', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
                return self::NOTIFY_CODE_SUCCESS;
            } else {
                Log::channel('payment-notify-error')->info($logName, ['err' => '订单已支付', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            }
            return self::NOTIFY_CODE_SUCCESS;
        }

        $paidAmount = intval(Arr::get($notifyDat, 'total_fee'));

        if ($paidAmount !== $paymentOrder->amount) {
            Log::channel('payment-notify-error')->info($logName, ['err' => "回调通知的支付金额与订单的支付金额不一致({$paidAmount}/{$paymentOrder->amount})", 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        try {

            if (Arr::get($notifyDat, 'pay_result') === '0') {

                $paymentOrder->paid_at = now()->toDateTimeString();
                $paymentOrder->out_trade_no = $outTransactionID;
                $paymentOrder->frozen_balance_amount = 0;
                $paymentOrder->frozen_point = 0;
                $paymentOrder->order_status = Constant::ORDER_STATUS_PAID;

                $paymentOrder->save();
                $paymentSuccessful = true;
            }
        } catch (\Throwable $exception) {
            Log::channel('payment-notify-error')->info($logName, ['err' => $exception->getMessage(), 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
        } finally {
            if ($paymentSuccessful) {
                //处理后续的各类系统订单逻辑
                PaymentOrderPaid::dispatch($paymentOrder)->onQueue(Constant::QUEUE_PAYMENT);

                return self::NOTIFY_CODE_SUCCESS;
            }
        }
        return self::NOTIFY_CODE_FAIL;
    }

    public function responseAdapter(array &$response = []): array
    {
        $status = Arr::get($response, 'status');
        $resultCode = Arr::get($response, 'result_code');

        if ($status === '0' && $resultCode === '0') {
            $response['result_code'] = $response['return_code'] = 'SUCCESS';
            $response['return_msg'] = 'OK';

            $response['trade_state_desc'] = strval(Arr::get($response, 'trade_state'));
        } else {
            $errCode = Arr::get($response, 'err_code');
            $errMsg = (Arr::get($response, 'message') ?: Arr::get($response, 'err_msg')) ?: 'ERROR';
            $response['result_code'] = $response['return_code'] = 'FAIL';
            $response['return_msg'] = $response['trade_state_desc'] = $errMsg;
            $response['trade_state'] = $errCode;
        }

        if ($payInfo = Arr::get($response, 'pay_info')) {
            $response['pay_info'] = json_decode($payInfo, true);
        }

        if ($codeImgUrl = Arr::get($response, 'code_img_url')) {
            $response['pay_info']['code_img_url'] = $codeImgUrl;
            unset($response['code_img_url']);
        }

        if ($codeUrl = Arr::get($response, 'code_url')) {
            $response['pay_info']['code_url'] = $codeUrl;
            unset($response['code_url']);
        }

        return $response;
    }

    public function paidCode(): string
    {
        return $this->snowflake->id();
    }

    public static function notifyDatFilter($rawNotifyDat): array
    {
        return [];
    }

    protected function notifySignCheck($rawNotifyDat): bool
    {
        if (!$notifySign = Arr::get($rawNotifyDat, 'sign')) {
            return false;
        }

        return $notifySign === $this->mkSignature($rawNotifyDat);
    }
}
